#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/time.h>

typedef struct client_s {
  int reqs;
  double bytes;
  long content_length;
  long content;
  double completes;
  double latency;
  struct timeval time;
  int socket;
  int state;
  int written;
  int req_len;
  char req[128];
  char file[128];
  char buffer[102400];
  char line[256];
  int line_off;
  int close;
} client_t;



// The Zipf stuff is adapted from the SEDA load tester (c) Matt Welsh

static int LOAD_CONNECTIONS = 1000; 
// Number of classes
#define NUM_CLASSES 4
// Number of directories - based on load value
static int NUMDIRS;
// Number of files
#define NUMFILES 8
// Zipf distribution table for directory
static double *DIR_ZIPF;
// Zipf distribution table for file
static double *FILE_ZIPF;

// Frequency of each class
static double CLASS_FREQ[] = { 0.35, 0.50, 0.14, 0.01 };
// Order of file popularity within each class
static int FILE_ORDER[] = { 4, 3, 5, 2, 6, 1, 7, 8, 0 };

static int MAX_REQS_PER_CONN = 5;
static int REQ_DELAY = 20;

char *host;
int port;

double *setupZipf(int size) {
  double *table = (double *)malloc(sizeof(double)*(size+1));
  double zipf_sum;
  int i;

  for (i = 1; i <= size; i++) {
    table[i] = (double)1.0 / (double)i;
  }

  zipf_sum = 0.0;
  for (i = 1; i <= size; i++) {
    zipf_sum += table[i];
    table[i] = zipf_sum;
  }
  table[size] = 0.0;
  table[0] = 0.0;
  for (i = 0; i < size; i++) {
    table[i] = 1.0 - (table[i] / zipf_sum);
  }
  return table;
}

void setupDists() {
  double opsps;
  int i;

  srand48(time(NULL));
  // Compute number of directories according to SPECweb99 rules
  opsps = (400000.0 / 122000.0) * LOAD_CONNECTIONS;
  NUMDIRS = (int)(25 + (opsps/5));
  DIR_ZIPF = setupZipf(NUMDIRS);
  FILE_ZIPF = setupZipf(NUMFILES);
  
  // Sum up CLASS_FREQ table 
  for (i = 1; i < NUM_CLASSES; i++) {
    CLASS_FREQ[i] += CLASS_FREQ[i-1];
  }
}

static int zipf(double table[]) {
  double r = drand48();
  int i = 0;
  while (r < table[i]) {
    i++;
  }
  return i-1;
}

#define REQ_TEMPLATE "GET %s HTTP/1.1\r\n\r\n"
#define CLOSE_TEMPLATE "GET %s HTTP/1.0\r\n\r\n"

void chooseURL(client_t *client) {
  int dir = zipf(DIR_ZIPF);
  int file = FILE_ORDER[ zipf(FILE_ZIPF) ];
  //char file_ak[30] = "/dir00006/class2_7";

  int theclass = 0;
  double d = drand48();
  while (d > CLASS_FREQ[theclass]) theclass++;

  //sprintf(client->file, "/dir%d.5/class%d_%d", dir, theclass, file);
  // Aruna: sprintf(client->file, "/dir%0.5d/class%d_%d", dir, theclass,file);
  int i=6;
  int j=2;
  int k=7;
  sprintf(client->file, "/dir%0.5d/class%d_%d", i,j,k );
  //client->file  = file_ak;
  //sprintf(client->file, "/index.html");
  //printf("%s\n", client->file);
  //sprintf(client->file, "/dir00000/class1_0", file);
  //printf("%d\n", client->reqs);
 if ((client->reqs+1) == MAX_REQS_PER_CONN)  {
	sprintf(client->req, CLOSE_TEMPLATE, client->file);
	//printf("close\n");
  }
   else 
	sprintf(client->req, REQ_TEMPLATE, client->file);
  client->req_len = strlen(client->req);
}

client_t *clients;
int client_count;
int client_connect(char *, short);

void client_done(client_t *client) {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  client->latency += (tv.tv_sec-client->time.tv_sec)*1000+
	((double)(tv.tv_usec-client->time.tv_usec))/1000;
  client->completes++;
  client->written = 0;
  client->state = 0;
  client->line_off = 0;
  client->content = 0;
  client->reqs++;
  if (client->reqs == MAX_REQS_PER_CONN) {
        client->reqs = 0;
	//shutdown(client->socket, SHUT_RDWR);
	//close(client->socket);
	//client->socket = client_connect(host, port);	
  }
  //usleep(REQ_DELAY*1000);
  chooseURL(client);
}

void parseHeader(client_t *client) {
  if (client->line_off == 0) { // end of headers
    client->state = 2; // Switch to content
  }
  else {
    client->line[client->line_off]=0;
    //printf(">%s\n", client->line);
    if (!strncmp(client->line, "Content-Length",14)) {
      client->content_length = atoi(client->line+16);
      //printf("%d\n", client->content_length);
    }
    /*
      else if (!strncmp(client->line, "Connection: close",17)) {
      client->close = 1;
      //printf("%Close\n");
      }
    */
    client->line_off = 0;
  }
}

void parse_data(client_t *client, int length) {
  int ix=0;
  //printf("data %d\n", length);
  while(ix<length && client->state == 1) {
    if (client->buffer[ix] == '\r') {
      ix++; // eat the '\n'
      client->bytes++;
      parseHeader(client);
    }
    else {
      client->line[client->line_off++] = client->buffer[ix];
    }
    ix++;
  }
  client->content+=length-ix;
  client->bytes+=length;
  //printf("got %d, expecting %d\n", client->content, client->content_length);
  if (client->content == client->content_length) {
    client_done(client);
  }
}

void client_read(client_t *client) {
  int len;
  
  if (client->state == 0)
    return;
  len = read(client->socket, client->buffer, 102400);
  if (len < 0) {
    perror("Read");
    exit(len);
  }
  if (len > 0) {
    parse_data(client, len);
  }
}

void client_write(client_t *client) {
  if (client->state == 0) { // Send request state 
    if (client->written < client->req_len) {
      int ret = 0;
      ret = write(client->socket, 
		  client->req+client->written, 
		  client->req_len-client->written);
      //printf("Write %d\n", ret);
      if (ret < 0) {
	perror("write");
	exit(ret);
      }
      client->written+=ret;
    }
    else {
      gettimeofday(&(client->time), NULL);
      client->state = 1; // Switch to reading headers
    }
 }
}

int client_connect(char *hst, short prt) {
  int sock;
  struct sockaddr_in sin;
  struct hostent *hp;
  
  bzero((char *)&sin, sizeof(sin));

  if (!(hp = gethostbyname(hst))) {
    perror("Look up");
    return -1;
  }
  
  //for (p = hp->h_addr_list; *p != 0; p++) {
  //struct in_addr in;
  //char **q;
  memcpy(&sin.sin_addr.s_addr, *(hp->h_addr_list),sizeof(sin.sin_addr.s_addr));
  //printf("%s\t\%s", inet_ntoa(sin.sin_addr), hp->h_name);
  //for (q=hp->h_aliases; *q != 0; q++)
  //printf(" %s", *q);
  //putchar('\n');
  //}
  sin.sin_family = AF_INET;
  sin.sin_port = htons(prt);
  memset(&(sin.sin_zero), '\0', 8);
 
  if ((sock=socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("Socket");
    return sock;
  }
  int nodelay = 1;
  if (setsockopt(sock, SOL_SOCKET, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay)) < 0) {
	perror("Option nodelay:");
	return -1;
  }
  int yes = 1;
  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)) == -1) {
    perror("Option reuse:");
    exit(1);
  }
  if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
    printf("Blah\n");
    printf("%s, %d\n", strerror(errno), errno);
    perror("Connect");
    printf("%s, %d", strerror(errno), errno);
    return -1;
  }
  return sock;
}

double mult = 8.0/1000000;

void statistics(struct timeval tme, struct timeval old_time) {
  int i=0;
  double bytes = 0;
  double latency = 0;
  double completes = 0;
  double div = tme.tv_sec-old_time.tv_sec+
    (tme.tv_usec-old_time.tv_usec)*0.000001;
  for(i=0;i<client_count;i++) {
    bytes += clients[i].bytes;
    latency += clients[i].latency;
    completes += clients[i].completes;
    clients[i].bytes = 0;
    clients[i].latency = 0;
    clients[i].completes = 0;
  }
  bytes = (bytes * mult)/div;
  latency = latency/completes;
  completes = completes/div;
  printf("%g Mb/s\t%g millis\t%g completes/s\n", bytes, latency, completes);
}
int MAX = 12;
int stats = 0;
int main(int argc, char **argv) {
  int fd_max = 0;
  int i;
  struct timeval tme, old_time;

  if (argc < 4) {
	fprintf(stderr, "Usage: %s <clients> <host> <port>\n", argv[0]);
	exit(-1);
  }

  setupDists();
  
  fd_set read_fds;
  fd_set write_fds;
  
  client_count = atoi(argv[1]);
  clients = (client_t *)malloc(sizeof(client_t)*client_count);
  bzero(clients, sizeof(client_t)*client_count);
  
  host = strdup(argv[2]);
  port = atoi(argv[3]);  
  for (i=0;i<client_count;i++) {
    chooseURL(&(clients[i]));
    clients[i].socket = client_connect(host, port);
    if (fd_max < clients[i].socket)
      fd_max = clients[i].socket;
    clients[i].reqs = 0;
    clients[i].completes = 0;
    clients[i].latency = 0;
  }
  fd_max += 1;

  gettimeofday(&old_time, NULL);
  printf("Starting select loop\n");
  while (1==1) {
    gettimeofday(&tme, NULL);
    if (tme.tv_sec-old_time.tv_sec > 5) {
      statistics(tme, old_time);
	stats++;
	if (stats == MAX) {
		exit(0);
	}
      old_time.tv_sec = tme.tv_sec;
      old_time.tv_usec = tme.tv_usec;
    }
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    
    for (i=0;i<client_count;i++) {
      FD_SET(clients[i].socket, &read_fds);
      FD_SET(clients[i].socket, &write_fds);
    }
    
    select(fd_max, &read_fds, &write_fds, NULL, NULL);

    for (i=0;i<client_count;i++) {
      if (FD_ISSET(clients[i].socket, &read_fds)) {
	//printf("read\n");
	client_read(&(clients[i]));
      }
      if (FD_ISSET(clients[i].socket, &write_fds)) {
	//printf("write\n");
	client_write(&(clients[i]));
      }
    }
  }
}
